package com.kancy.spring.boot;

import com.kancy.spring.context.ApplicationContext;
import com.kancy.spring.context.ApplicationContextInitializer;
import com.kancy.spring.context.SimpleApplicationContext;
import com.kancy.spring.context.SimpleApplicationContextInitializer;
import com.kancy.spring.context.annotation.Order;
import com.kancy.spring.log.Logger;
import com.kancy.spring.log.LoggerFactory;
import com.kancy.spring.utils.SpringUtils;

import java.util.*;

/**
 * SpringBootApplication
 *
 * @author kancy
 * @date 2020/2/18 12:12
 */
public class SpringApplication {
    private Class<?> applicationEntryClass;

    private ApplicationContext applicationContext;

    private final Logger log = LoggerFactory.getLogger(SpringApplication.class);

    public SpringApplication(Class<?> applicationEntryClass) {
        this.applicationEntryClass = applicationEntryClass;
    }

    public static void run(Class<?> applicationEntryClass, String[] args) {
        new SpringApplication(applicationEntryClass).run(args);
    }

    public void run(String[] args) {
        log.info("start running......");

        // create application context and application initializer
        applicationContext = createSimpleApplicationContext();

        SpringUtils.setApplicationContext(applicationContext);
        ApplicationContextInitializer initializer = createSimpleApplicationContextInitializer(applicationEntryClass);

        // initialize the application context (this is where we create beans)
        // here maybe exist a hidden cast
        initializer.initialize(applicationContext);

        // process those special beans
        processSpecialBeans(args);

        log.info("Started {} success in {} seconds!",
                applicationContext.getEnvironment().getApplicationName(),
                (System.currentTimeMillis() *1.0 - applicationContext.getStartupDate())/1000);
    }

    private SimpleApplicationContextInitializer createSimpleApplicationContextInitializer(Class<?> entryClass) {
        // get base packages
        SpringBootApplication annotation = entryClass.getDeclaredAnnotation(SpringBootApplication.class);
        String[] basePackages = annotation.basePackages();
        if (basePackages.length == 0) {
            basePackages = new String[]{entryClass.getPackage().getName()};
        }

        // create context initializer with base packages
        return new SimpleApplicationContextInitializer(Arrays.asList(basePackages));
    }

    private SimpleApplicationContext createSimpleApplicationContext() {
        return new SimpleApplicationContext();
    }

    private void processSpecialBeans(String[] args) {
        callRegisteredRunners(args);
    }

    private void callRegisteredRunners(String[] args) {
        Map<String, ApplicationRunner> applicationRunners = applicationContext.getBeansOfType(ApplicationRunner.class);
        List<ApplicationRunner> applicationRunnerList = sortByOrder(applicationRunners.values());
        try {
            for (ApplicationRunner applicationRunner : applicationRunnerList) {
                applicationRunner.run(args);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 通过Order注解排序
     * @param collection
     */
    private <T> List<T> sortByOrder(Collection<T> collection) {
        List<T> list = new ArrayList<T>(collection);
        Collections.sort(list, new Comparator<Object>() {
            @Override
            public int compare(Object o1, Object o2) {
                return getPriority(o1) - getPriority(o2);
            }
            private int getPriority(Object o1) {
                Order order = o1.getClass().getAnnotation(Order.class);
                int priority = Integer.MAX_VALUE;
                if (Objects.nonNull(order)) {
                    priority = order.value();
                }
                return priority;
            }
        });
        return list;
    }
}